package com.jokerku.lottery.interfaces.idempotent.aop;

import com.jokerku.lottery.infrastructure.utils.RedisUtil;
import com.jokerku.lottery.interfaces.idempotent.annotation.Lock;
import com.jokerku.lottery.interfaces.idempotent.cache.CacheService;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.*;
import java.util.Locale;
import java.util.Objects;

/**
 * @Author: guzq
 * @CreateTime: 2022/11/06 18:05
 * @Description: 幂等拦截器
 * @Version: 1.0
 */
@Component
@Aspect
public class IdempotentAop {

    private final Logger log = LoggerFactory.getLogger(IdempotentAop.class);

    @Resource
    private CacheService localCacheService;

    @Pointcut("@annotation(com.jokerku.lottery.interfaces.idempotent.annotation.Lock)")
    public void pointcut() {
    }

    @Around(value = "pointcut()")
    public Object preHandle(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        Lock lock = AnnotationUtils.findAnnotation(method, Lock.class);
        if (Objects.isNull(lock)) {
            return joinPoint.proceed();
        }
        String idempotentName = lock.value();
        if (StringUtils.isBlank(idempotentName)) {
            throw new RuntimeException("幂等键不能为空");
        }
        if (!StringUtils.startsWith(idempotentName, "#")) {
            throw new RuntimeException("幂等键需要以#开头");
        }
        // eg: #idempotentVO.name
        String objectName = StringUtils.substringBetween(idempotentName, "#", ".");
        String propertyName = StringUtils.substring(idempotentName, idempotentName.indexOf(".") + 1, idempotentName.length());
        String propertyGetMethod = "get" + propertyName.substring(0, 1).toUpperCase(Locale.ENGLISH) + propertyName.substring(1);

        int index = -1;
        for (int i = 0; i < method.getParameters().length; i++) {
            Parameter parameter = method.getParameters()[i];
            if (parameter.getName().equals(objectName)) {
                index = i;
                break;
            }
        }
        if (index < 0) {
            throw new RuntimeException("幂等键对象不存在");
        }

        Object[] args = joinPoint.getArgs();
        Object arg = args[index];
        Method propertyMethod = arg.getClass().getMethod(propertyGetMethod);
        Object value = propertyMethod.invoke(arg, new Objects[]{});

        if (localCacheService.setNX(value.toString(), lock.expireTime())) {
            log.info("获取锁成功：{}", Thread.currentThread().getName());
            try {
                return joinPoint.proceed();
            } finally {
                localCacheService.del(value.toString());
            }
        } else {
            log.info("命中幂等：{}", Thread.currentThread().getName());
            throw new RuntimeException(lock.errorDesc());
        }

    }

}
